package dd.prog2.musterExercises.ws12set13_4_mvc.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;

import dd.prog2.musterExercises.ws12set13_4_mvc.game.AdversarialSearch;
import dd.prog2.musterExercises.ws12set13_4_mvc.game.ConnectFourGame;
import dd.prog2.musterExercises.ws12set13_4_mvc.game.ConnectFourPlayer;
import dd.prog2.musterExercises.ws12set13_4_mvc.game.ConnectFourState;
import dd.prog2.musterExercises.ws12set13_4_mvc.game.Metrics;

/**
 * First Version of a GUI for the Connect Four game with integrated artificial
 * intelligence player. Here, all aspects of the GUI got mixed up. Besides, the
 * application demonstrates the Minimax algorithm with alpha-beta pruning,
 * iterative deepening, and action ordering. The implemented action ordering
 * strategy tries to maximize the impact of the chosen action for later game
 * phases.
 * 
 * @author Ruediger Lunde
 * 
 */
public class ConnectFourApp {

	// create attributes for all parts of the application which need to be
	// accessed by listeners, inner classes, and helper methods

	private JFrame frame;
	private JComboBox strategyCombo;
	private JComboBox timeCombo;
	private JButton clearButton;
	private JButton proposeButton;
	private JPanel boardPanel;
	private JLabel statusBar;

	private ConnectFourGame game;
	private ConnectFourState currState;
	Metrics searchMetrics;

	/** Creates a game object, constructs the game frame and makes it visible. */
	public void start() {
		game = new ConnectFourGame();
		currState = (ConnectFourState) game.getInitialState();

		frame = new JFrame("Connect Four");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		AppActionListener listener = new AppActionListener();
		JToolBar toolBar = new JToolBar();
		toolBar.setFloatable(false);
		strategyCombo = new JComboBox(new String[] { "Advanced Alpha-Beta",
				"Advanced Alpha-Beta (debug)" });
		strategyCombo.setSelectedIndex(0);
		toolBar.add(strategyCombo);
		timeCombo = new JComboBox(new String[] { "5sec", "10sec", "15sec",
				"20sec" });
		timeCombo.setSelectedIndex(0);
		toolBar.add(timeCombo);
		toolBar.add(Box.createHorizontalGlue());
		clearButton = new JButton("Clear");
		clearButton.addActionListener(listener);
		toolBar.add(clearButton);
		proposeButton = new JButton("Propose Move");
		proposeButton.addActionListener(listener);
		toolBar.add(proposeButton);
		frame.add(toolBar, BorderLayout.NORTH);

		int rows = currState.getRows();
		int cols = currState.getCols();
		boardPanel = new JPanel();
		boardPanel.setLayout(new GridLayout(rows, cols, 5, 5));
		boardPanel.setBorder(BorderFactory.createEtchedBorder());
		boardPanel.setBackground(Color.BLUE);
		for (int i = 0; i < rows * cols; i++) {
			GridElement element = new GridElement(i / cols, i % cols);
			boardPanel.add(element);
			element.addActionListener(listener);
		}
		frame.add(boardPanel, BorderLayout.CENTER);
		statusBar = new JLabel();
		frame.getContentPane().setBackground(Color.BLUE);
		statusBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		frame.add(statusBar, BorderLayout.SOUTH);

		updateStatus();
		frame.setSize(450, 450);
		frame.setVisible(true);
	}

	private boolean isDebugModeEnabled() {
		return strategyCombo.getSelectedIndex() == 1;
	}

	/** Uses adversarial search for selecting the next action. */
	public void proposeMove() {
		AdversarialSearch<ConnectFourState, Integer> search;
		Integer action;
		int time = (timeCombo.getSelectedIndex() + 1) * 5;
		switch (strategyCombo.getSelectedIndex()) {
		case 0:
			search = new ConnectFourPlayer(game, time);
			break;
		default:
			search = new ConnectFourPlayer(game, time);
			((ConnectFourPlayer) search).setLogEnabled(true);
		}
		action = search.makeDecision(currState);
		if (isDebugModeEnabled())
			searchMetrics = search.getMetrics();
		currState = game.getResult(currState, action);
	}

	/** Updates the status bar. */
	private void updateStatus() {
		String statusText;
		if (!game.isTerminal(currState)) {
			String toMove = (String) game.getPlayer(currState);
			statusText = "Next move: " + toMove;
			statusBar.setForeground(toMove.equals("red") ? Color.RED
					: Color.YELLOW);
		} else {
			String winner = null;
			for (int i = 0; i < 2; i++)
				if (game.getUtility(currState, game.getPlayers()[i]) == 1)
					winner = game.getPlayers()[i];
			if (winner != null)
				statusText = "Color " + winner + " has won. Congratulations!";
			else
				statusText = "No winner :-(";
			statusBar.setForeground(Color.WHITE);
		}
		if (searchMetrics != null)
			statusText += "    " + searchMetrics;
		statusBar.setText(statusText);
	}

	/** Creates the Connect Four Application and starts it. */
	public static void main(String[] args) {
		ConnectFourApp gameApp = new ConnectFourApp();
		gameApp.start();
	}

	// //////////////////////////////////////////////////////////////
	// inner classes

	/** Handles all button events and updates the view. */
	private class AppActionListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			searchMetrics = null;
			if (e == null || e.getSource() == clearButton) {
				currState = game.getInitialState();
			} else if (!game.isTerminal(currState)) {
				if (e.getSource() == proposeButton) {
					proposeMove();
				} else if (e.getSource() instanceof GridElement) {
					GridElement el = (GridElement) e.getSource();
					currState = game.getResult(currState, el.col); // take next
																	// turn
				}
			}
			boardPanel.repaint(); // paint all disks!
			updateStatus();
		}
	}

	/** Represents a space within the grid where discs can be placed. */
	@SuppressWarnings("serial")
	private class GridElement extends JButton {
		int row;
		int col;

		GridElement(int row, int col) {
			this.row = row;
			this.col = col;
			setBackground(Color.BLUE);
		}

		public void paintComponent(Graphics g) {
			super.paintComponent(g); // should have look and feel of a button...
			int playerNum = currState.getPlayerNum(row, col);
			if (playerNum != 0) {
				drawDisk(g, playerNum); // draw disk on top!
			}
			if (isDebugModeEnabled())
				for (int pNum = 1; pNum <= 2; pNum++)
					if (currState.isWinPositionFor(row, col, pNum))
						drawWinSituation(g, pNum);
		}

		/** Fills a simple oval. */
		void drawDisk(Graphics g, int playerNum) {
			int size = Math.min(getWidth(), getHeight());
			g.setColor(playerNum == 1 ? Color.RED : Color.YELLOW);
			g.fillOval((getWidth() - size) / 2, (getHeight() - size) / 2, size,
					size);
		}

		/** Draws a simple oval. */
		void drawWinSituation(Graphics g, int playerNum) {
			int size = Math.min(getWidth(), getHeight());
			g.setColor(playerNum == 1 ? Color.RED : Color.YELLOW);
			g.drawOval((getWidth() - size) / 2 + playerNum,
					(getHeight() - size) / 2 + playerNum, size - 2 * playerNum,
					size - 2 * playerNum);
		}
	}
}
